/*
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package commandhandlers;

import input.IRCCommand;
import interfaces.CommandHandler;
import interfaces.TextHandler;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;

import main.Bot;
import main.CommandSettings;
import main.CommonMethods;
import main.Constants;
import main.TopicCentral;
import main.UserCentral;

import org.apache.log4j.Logger;
import org.jibble.pircbot.Colors;

import output.IRCOutput;
import output.OutputTypeEnum;
import timers.HelpTimer;
import tools.InputType;
import tools.UtopiaTime;
import botobjects.Command;
import botobjects.HelpTopic;
import botobjects.Link;
import database.DBConnectionFactory;
import exceptions.UtoDateException;

/**
 * Handles help type of commands
 * 
 * 
 */
public class HelpHandler extends CommandHandler implements TextHandler {
   private static HelpHandler INSTANCE = null;

   /**
    * Returns the syntax for the specified building
    * 
    * @param building
    * @return
    */
   public static List<String> getBuildingSyntax(final String building) {
	  List<String> out = new LinkedList<String>();
	  PreparedStatement ps = null;
	  ResultSet rs = null;
	  try {
		 ps = DBConnectionFactory.getConnectionHandler().getConnection()
			   .prepareStatement("SELECT * FROM building WHERE name = ? OR abbreviation = ?");
		 ps.setString(1, building);
		 ps.setString(2, building);
		 rs = ps.executeQuery();

		 if (rs.next()) {
			out.add(rs.getString("name") + " syntax: !effects " + rs.getString("abbreviation") + " " + rs.getString("syntax"));
		 }
	  } catch (final SQLException e) {
		 Logger.getLogger("Error").error("", e);
	  } finally {
		 try {
			rs.close();
		 } catch (final Exception e) {
		 }
		 try {
			ps.close();
		 } catch (final Exception e) {
		 }
	  }
	  return out;
   }

   /**
    * Returns a list of Commands. Either all commands or just those of a certain command type. Help is included in each of the
    * Command objects, and so help can be printed out from them.
    * 
    * @param args
    * @return
    */
   public static List<Command> getHelp(final String... args) {
	  List<Command> out = new LinkedList<Command>();
	  PreparedStatement ps = null;
	  ResultSet rs = null;
	  try {
		 if (args.length == 0) {
			ps = DBConnectionFactory.getConnectionHandler().getConnection().prepareStatement("SELECT * FROM commands");
		 } else {
			ps = DBConnectionFactory.getConnectionHandler().getConnection()
				  .prepareStatement("SELECT * FROM commands WHERE cmdtype = ?");
			ps.setString(1, args[0]);
		 }
		 rs = ps.executeQuery();

		 while (rs.next()) {
			out.add(new Command(rs.getString("name"), rs.getString("aliases"), rs.getString("help"), rs.getString("syntax"), rs
				  .getString("cmdtype"), rs.getString("classname"), rs.getString("classurl"), rs.getInt("priority"), rs
				  .getBoolean("bypassantispam"), rs.getBoolean("publicaccess"), OutputTypeEnum.fromString(rs
				  .getString("delivery"))));
		 }
	  } catch (final SQLException e) {
		 Logger.getLogger("Error").error("", e);
	  } finally {
		 try {
			rs.close();
		 } catch (final Exception e) {
		 }
		 try {
			ps.close();
		 } catch (final Exception e) {
		 }
	  }
	  return out;
   }

   public static final HelpHandler getInstance() {
	  if (INSTANCE == null) {
		 INSTANCE = new HelpHandler();
	  }
	  return INSTANCE;
   }

   /**
    * Just used to print the help in html so I can put it on the website
    * 
    * @param args
    */
   public static void main(final String[] args) {
	  Connection con = null;
	  try {
		 Class.forName("com.mysql.jdbc.Driver").newInstance();
	  } catch (Exception e1) {
		 e1.printStackTrace();
	  }
	  Statement stmt = null;
	  try {
		 con = DriverManager.getConnection("jdbc:mysql://" + "127.0.0.1" + "/", "root", "835501");
		 stmt = con.createStatement();
		 stmt.execute("use " + "bot");
	  } catch (SQLException e) {
		 // it's possible that we've already been redirected to the correct db
	  } finally {
		 try {
			stmt.close();
		 } catch (final Exception e) {
		 }
	  }
	  PreparedStatement ps = null;
	  ResultSet rs = null;
	  try {
		 ps = con.prepareStatement("SELECT DISTINCT cmdtype FROM commands ORDER BY cmdtype");
		 rs = ps.executeQuery();

		 System.out.println("<html>");
		 System.out.println("<body>");
		 System.out.println("<h1><a name=help>LucidBot Help</a></h1>");
		 System.out.println("<h2>");
		 while (rs.next()) {
			System.out.println("<a href=\"#" + rs.getString("cmdtype") + "\">" + rs.getString("cmdtype") + "</a>");
		 }
		 System.out.println("</h2>");

		 try {
			ps.close();
		 } catch (Exception e) {
		 }

		 ps = con.prepareStatement("SELECT * FROM commands ORDER BY cmdtype,name");
		 rs = ps.executeQuery();

		 String cmdtype = "";
		 while (rs.next()) {
			if (!cmdtype.equals(rs.getString("cmdtype"))) {
			   cmdtype = rs.getString("cmdtype");
			   System.out.println("<h2><a name=" + cmdtype + ">" + cmdtype + "</a></h2><a href=\"#help\"> back to top</a>");
			}
			System.out.println("<p>");
			System.out.println("<b>" + rs.getString("name") + "</b> - " + rs.getString("help"));
			System.out.println("<br>");
			System.out.println("Syntax: " + rs.getString("syntax").replace("<", "&lt;").replace(">", "&gt;"));
			System.out.println("</p>");
		 }
		 System.out.println("</body>");
		 System.out.println("</html>");
	  } catch (Exception e) {

	  } finally {
		 try {
			rs.close();
		 } catch (Exception e) {
		 }
		 try {
			ps.close();
		 } catch (Exception e) {
		 }
		 try {
			con.close();
		 } catch (Exception e) {
		 }
	  }
   }

   public static final HelpHandler newInstance() {
	  INSTANCE = null;
	  return getInstance();
   }

   /**
    * Sets syntax for the specified type of building
    * 
    * @param building
    * @param syntax
    * @return
    */
   public static boolean setBuildingSyntax(final String building, final String syntax) {
	  boolean out = false;
	  PreparedStatement ps = null;
	  try {
		 ps = DBConnectionFactory.getConnectionHandler().getConnection()
			   .prepareStatement("UPDATE building SET " + "syntax = ? WHERE name = ? OR abbreviation = ?");
		 ps.setString(1, syntax);
		 ps.setString(2, building);
		 ps.setString(3, building);

		 if (ps.executeUpdate() == 1) {
			out = true;
		 }
	  } catch (final SQLException e) {
		 Logger.getLogger("Error").error("", e);
	  } finally {
		 try {
			ps.close();
		 } catch (final Exception e) {
		 }
	  }
	  return out;
   }

   private final Map<String, LinkedList<String>> helpAdd;

   private HelpHandler() {
	  helpAdd = new HashMap<String, LinkedList<String>>();
   }

   /**
    * Returns a list of the different command types registered in the database, referred to as Areas
    * 
    * @return
    */
   private List<String> getAreas() {
	  List<String> out = new LinkedList<String>();
	  PreparedStatement ps = null;
	  ResultSet rs = null;
	  try {
		 ps = DBConnectionFactory.getConnectionHandler().getConnection().prepareStatement("SELECT cmdtype FROM commands");
		 rs = ps.executeQuery();

		 String temp = "Command areas: ";
		 while (rs.next()) {
			if (!temp.matches(".*" + rs.getString("cmdtype") + ".*")) {
			   temp += rs.getString("cmdtype") + ", ";
			}
		 }
		 out.add(temp.substring(0, temp.length() - 2));

		 try {
			ps.close();
		 } catch (final Exception e) {
		 }

		 ps = DBConnectionFactory.getConnectionHandler().getConnection()
			   .prepareStatement("SELECT topic FROM " + "help WHERE helptext = '' OR mastertopic = ''");
		 rs = ps.executeQuery();

		 temp = "Help topics: ";
		 while (rs.next()) {
			temp += rs.getString("topic") + ", ";
		 }
		 out.add(temp.substring(0, temp.length() - 2));

		 out.add("Use !help <area/topic> to see the help");
	  } catch (final SQLException e) {
		 Logger.getLogger("Error").error("", e);
	  } finally {
		 try {
			rs.close();
		 } catch (final Exception e) {
		 }
		 try {
			ps.close();
		 } catch (final Exception e) {
		 }
	  }

	  return out;
   }

   /**
    * Checks in the database for carriers and the sms-to-email addy they have
    */
   private List<String> getCarrier(final String carrier) {
	  List<String> out = new LinkedList<String>();
	  PreparedStatement ps = null;
	  ResultSet rs = null;
	  try {
		 ps = DBConnectionFactory.getConnectionHandler().getConnection()
			   .prepareStatement("SELECT * FROM carrier WHERE name LIKE ? LIMIT 5");
		 ps.setString(1, "%" + carrier + "%");
		 rs = ps.executeQuery();

		 out.add("Possible carriers:");
		 while (rs.next()) {
			out.add("Name: " + rs.getString("name") + " | E-mail address: " + rs.getString("mail"));
		 }
	  } catch (final SQLException e) {
		 Logger.getLogger("Error").error("", e);
	  } finally {
		 try {
			rs.close();
		 } catch (final Exception e) {
		 }
		 try {
			ps.close();
		 } catch (final Exception e) {
		 }
	  }
	  return out;
   }

   @Override
   public void handle(final IRCCommand command, final IRCOutput output) {
   }

   public void handleAddhelp(final IRCCommand command, final IRCOutput out) {
	  String user = UserCentral.getInstance().getOnlineUser(command.getSender());
	  if (command.size() == 2 && command.get(1).equalsIgnoreCase("done") && helpAdd.containsKey(user)) {
		 Bot.getInstance().getTimerHandler().removeHelpTimer(user);
		 List<String> help = helpAdd.remove(user);
		 String helptext = "";
		 String masterTopic = "";
		 for (int i = 1; i < help.size(); ++i) {
			if (help.get(i).matches("(?i)master topic .*")) {
			   masterTopic = help.get(i).replaceFirst("(?i)master topic ", "");
			} else {
			   helptext += help.get(i) + "\n";
			}
		 }
		 if (new HelpTopic(help.get(0), helptext, masterTopic).saveToDB()) {
			out.addOutput("Help topic added");
		 }
	  } else if (command.size() == 2 && command.get(1).equalsIgnoreCase("cancel") && helpAdd.containsKey(user)) {
		 Bot.getInstance().getTimerHandler().removeHelpTimer(user);
		 out.addOutput("Help adding cancelled");
		 helpAdd.remove(user);
	  } else if (command.size() > 1) {
		 Bot.getInstance().getTimerHandler().addHelpTimer(new HelpTimer(user, this));
		 LinkedList<String> help = new LinkedList<String>();
		 help.add(command.getFullMessage());
		 helpAdd.put(user, help);
		 out.addOutput("Finish the help adding by calling !addhelp done");
		 out.addOutput("If you stop without calling !addhelp done or !addhelp cancel, you need "
			   + "to wait 30 seconds before you can pm the bot attacks and ops and such");
	  }
   }

   public void handleAddlink(final IRCCommand command, final IRCOutput out) {
	  if (UserCentral.getInstance().onlineUserIsAdmin(command.getSender(), command.getChannel()) && command.size() >= 3
			&& new Link(command.getPartialMessage(1, command.size() - 1), command.get(command.size() - 1)).saveToDB()) {
		 out.addOutput("Link added");
	  }
   }

   public void handleAliases(final IRCCommand command, final IRCOutput out) {
	  if (command.size() == 2) {
		 String baseCmd = CommandSettings.INSTANCE.getBaseCommand(command.get(1));
		 Command cmd = Command.getCommand(baseCmd);
		 if (cmd != null) {
			out.addOutput(cmd.getIRCOutput(Constants.COMMAND_GET_ALIASES));
		 }
	  }
   }

   public void handleCarrier(final IRCCommand command, final IRCOutput out) {
	  if (command.size() > 1) {
		 out.addOutput(getCarrier(command.getFullMessage()));
	  }
   }

   public void handleGmt(final IRCCommand command, final IRCOutput out) {
	  TimeZone tz = TimeZone.getTimeZone("GMT");
	  String pattern = "EEE, MMM d, H:mm:ss";
	  SimpleDateFormat sdf = new SimpleDateFormat(pattern, Locale.US);
	  sdf.setTimeZone(tz);
	  String output = sdf.format(new Date(GregorianCalendar.getInstance().getTimeInMillis()));
	  out.addOutput(Colors.OLIVE + "Time in GMT: " + Colors.BLUE + output);
   }

   public void handleHelp(final IRCCommand command, final IRCOutput out) {
	  if (command.size() == 1) {
		 out.addOutput(getAreas());
	  } else {
		 String choice = command.getFullMessage();
		 List<Command> commands = getHelp(choice);
		 if (commands.isEmpty()) {
			HelpTopic ht = HelpTopic.getHelpTopic(choice);
			if (ht != null) {
			   out.addOutput(ht.getIRCOutput(0));
			}
		 } else {
			for (Command cmd : commands) {
			   out.addOutput(cmd.getIRCOutput(Constants.COMMAND_GET_HELP));
			}
			out.addOutput("Use !syntax <command> to see how to use a specific command");
		 }
	  }
   }

   public void handleLink(final IRCCommand command, final IRCOutput out) {
	  if (command.size() == 1) {
		 for (Link link : Link.getLinks()) {
			out.addOutput(link.getIRCOutput(0));
		 }
	  } else if (command.size() >= 2
			&& command.getFullMessage().toLowerCase().matches(CommonMethods.getLinksRegex().toLowerCase())) {
		 Link link = Link.getLink(command.getFullMessage());
		 if (link != null) {
			out.addOutput(link.getIRCOutput(0));
		 }
	  }
   }

   public void handleRemovehelp(final IRCCommand command, final IRCOutput out) {
	  if (command.size() > 1 && UserCentral.getInstance().onlineUserIsAdmin(command.getSender(), command.getChannel())
			&& HelpTopic.deleteFromDB(command.getFullMessage())) {
		 out.addOutput("Help topic removed");
	  }
   }

   public void handleRemovelink(final IRCCommand command, final IRCOutput out) {
	  if (UserCentral.getInstance().onlineUserIsAdmin(command.getSender(), command.getChannel()) && command.size() >= 2
			&& Link.deleteFromDB(command.getFullMessage())) {
		 out.addOutput("Link removed");
	  }
   }

   public void handleSethelp(final IRCCommand command, final IRCOutput out) {
	  if (command.size() > 2 && UserCentral.getInstance().onlineUserIsAdmin(command.getSender(), command.getChannel())) {
		 String baseCmd = CommandSettings.INSTANCE.getBaseCommand(command.get(1));
		 if ("".equals(baseCmd))
			return;
		 Command temp = Command.getCommand(baseCmd);
		 if (temp == null)
			return;
		 temp.setHelp(command.getPartialMessage(2, command.size()));
		 if (temp.saveToDB()) {
			out.addOutput("Help updated");
		 }
	  }
   }

   public void handleSetsyntax(final IRCCommand command, final IRCOutput out) {
	  if (command.size() > 2 && UserCentral.getInstance().onlineUserIsAdmin(command.getSender(), command.getChannel())) {
		 String buildingsRegex = CommonMethods.getBuildingsRegex().toLowerCase();
		 String msg = command.getFullMessage();
		 if (msg.toLowerCase().matches("effects " + buildingsRegex + " .*")) {
			if (setBuildingSyntax(command.get(2), command.getPartialMessage(3, command.size()))) {
			   out.addOutput("Syntax updated");
			}
		 } else {
			String baseCmd = CommandSettings.INSTANCE.getBaseCommand(command.get(1));
			if ("".equals(baseCmd))
			   return;
			Command temp = Command.getCommand(baseCmd);
			if (temp == null)
			   return;
			temp.setSyntax(command.getPartialMessage(2, command.size()));
			if (temp.saveToDB()) {
			   out.addOutput("Syntax updated");
			}
		 }
	  }
   }

   public void handleSyntax(final IRCCommand command, final IRCOutput out) {
	  if (command.size() > 1) {
		 if (command.get(1).equalsIgnoreCase("effects") && command.size() > 2) {
			out.addOutput(getBuildingSyntax(command.getPartialMessage(2, command.size())));
		 } else {
			String baseCmd = CommandSettings.INSTANCE.getBaseCommand(command.get(1));
			Command cmd = Command.getCommand(baseCmd);
			if (cmd != null) {
			   out.addOutput(cmd.getIRCOutput(Constants.COMMAND_GET_SYNTAX));
			}
		 }
	  }
   }

   public boolean handleText(final String message, final String channel, final String sender, final IRCOutput out) {
	  String user = UserCentral.getInstance().getOnlineUser(sender);
	  if (helpAdd.containsKey(user)) {
		 LinkedList<String> temp = helpAdd.get(user);
		 temp.add(message);
		 helpAdd.put(user, temp);
		 return true;
	  }
	  return false;
   }

   public void handleTime(final IRCCommand command, final IRCOutput out) {
	  UtopiaTime uttime;
	  if (command.size() > 3) {
		 if (command.validate(1, 4, InputType.UTODATE)) {
			try {
			   uttime = new UtopiaTime(command.getPartialMessage(1, 4));
			   Date irlTime = new Date(uttime.getTime());
			   TimeZone tz = TimeZone.getTimeZone("GMT"
					 + CommonMethods.getUserTimeZone(UserCentral.getInstance().getOnlineUser(command.getSender())));

			   String pattern = "EEE, MMM d, H:mm:ss";
			   SimpleDateFormat sdf = new SimpleDateFormat(pattern, Locale.US);
			   sdf.setTimeZone(tz);
			   out.addOutput(Colors.OLIVE + "In your local time: " + Colors.BLUE + sdf.format(irlTime));
			   if (uttime.getTime() < GregorianCalendar.getInstance().getTimeInMillis()) {
				  out.addOutput(Colors.OLIVE + "That uto date was: " + Colors.BLUE + UtopiaTime.compareTimes(uttime.getTime())
						+ " ago");
			   } else {
				  out.addOutput(Colors.OLIVE + "That uto date is: " + Colors.BLUE + UtopiaTime.compareTimes(uttime.getTime())
						+ " from now");
			   }
			} catch (final UtoDateException e) {
			   Logger.getLogger("Error").error("", e);
			}
		 }
	  } else if (command.size() == 2) {
		 String user = CommonMethods.getUserFromDB(command.get(1), false);
		 if ("".equals(user))
			return;
		 else {
			TimeZone tz = TimeZone.getTimeZone("GMT" + CommonMethods.getUserTimeZone(user));
			String pattern = "EEE, MMM d, H:mm:ss";
			SimpleDateFormat sdf = new SimpleDateFormat(pattern, Locale.US);
			sdf.setTimeZone(tz);
			out.addOutput(Colors.BLUE + user + "'s" + Colors.OLIVE + " local time: " + Colors.BLUE
				  + sdf.format(new Date(GregorianCalendar.getInstance().getTimeInMillis())));
		 }
	  } else {
		 uttime = new UtopiaTime(GregorianCalendar.getInstance().getTimeInMillis());
		 out.addOutput(Colors.OLIVE + "Current utotime: " + Colors.BLUE + uttime.formattedUT());
		 out.setDelivery(OutputTypeEnum.RETURN_TO_SOURCE_MESSAGE);
	  }
   }

   public void handleTopic(final IRCCommand command, final IRCOutput out) {
	  TopicCentral.getInstance().getTopic(command.getChannel());
   }

   /**
    * Checks whether the specified user is registered as adding help
    * 
    * @param user
    * @return
    */
   public boolean isAddingHelp(final String user) {
	  return helpAdd.containsKey(user);
   }

   /**
    * Removes a user from the map that keeps track of who is currently adding help
    * 
    * @param user
    */
   public void removeHelpAdd(final String user) {
	  helpAdd.remove(user);
   }

   @Override
   public boolean willHandleText(final String message, final String channel, final String sender) {
	  return helpAdd.containsKey(UserCentral.getInstance().getOnlineUser(sender));
   }
}
